
#include <apr_file_io.h>
#include "../net/Broker4.h"
#include "../crypt/RSAKeyPair.h"
#include "../utils/BaseConvert.h"
#include "../utils/StringUtils.h"
#include "../utils/StreamFromDirectory.h"
#include "XMLDescriptor.h"

XMLDescriptor::XMLDescriptor()
{
   domd = new DOMDocument();
}

XMLDescriptor::~XMLDescriptor()
{
   if (domd) delete domd;
}

int XMLDescriptor::ReadEncrypted(KeyDB *keydb, const char *keyname, const char *filename, apr_pool_t *mp)
{
   apr_status_t status;
   apr_file_t *newFP;
   status = apr_file_open(&newFP, StringUtils::expandMyHomeDirectoryTilde(filename, mp), APR_FOPEN_READ|APR_FOPEN_BINARY|APR_FOPEN_BUFFERED, APR_FPROT_OS_DEFAULT, mp);
   if (status != APR_SUCCESS) { CheckAPRError(status); return 0; }
   apr_finfo_t finfo;
#ifdef USE_STAT_FILE_SIZE
   status = apr_file_info_get(&finfo, APR_FINFO_SIZE, newFP);
   CheckAPRError(status);
#else
   apr_off_t offset = 0;
   status = apr_file_seek(newFP, APR_END, &offset);
   CheckAPRError(status);
   finfo.size = offset;
   offset = 0;
   status = apr_file_seek(newFP, APR_SET, &offset);
   CheckAPRError(status);
#endif
   if (finfo.size == 0) { errprint("tried to load zero length file"); return 0; }
   apr_size_t fullread = finfo.size;
   char *encd = (char *)malloc(sizeof(char)*(finfo.size));
   status = apr_file_read_full(newFP, encd, finfo.size, &fullread);
   CheckAPRError(status);
   if (fullread != finfo.size)
   {
      errprint("expected to read %d bytes, actually read %d", finfo.size, fullread);
      free(encd);
      return 0;
   }
   status = apr_file_close(newFP);
   CheckAPRError(status);
   KeyPair *kp = keydb->GetPrivateKeyByName(keyname);
   int trustdata = 0;
   std::vector<char> *decdata = Broker4::SimpleCryptDataChunk(keyname, NULL, (RSAKeyPair *)kp, NULL, encd, fullread, 1, 0, mp, &trustdata);
   free(encd);
   if ((decdata == NULL) || (decdata->size() == 0))
   {
      errprint("decryption of data failed from XML descriptor");
      if (decdata) delete decdata;
      if (kp) delete kp;
      return 0;
   }
   domd->ReadIn(&(decdata->at(0)), decdata->size());
   if (kp) delete kp;
   delete decdata;
}

int XMLDescriptor::WriteEncrypted(KeyDB *keydb, const char *keyname, const char *filename, apr_pool_t *mp)
{
   KeyPair *kp = keydb->GetPublicKeyByName(keyname);
   int trustdata = 0;
   xmlBufferPtr xbp = xmlBufferCreate();
   domd->WriteOut(xbp);
   std::vector<char> *encdata = Broker4::SimpleCryptDataChunk(keyname, NULL, (RSAKeyPair *)kp, NULL, (char *)xmlBufferContent(xbp), xmlBufferLength(xbp), 0, 0, mp, &trustdata);
   if (kp) delete kp;
   xmlBufferFree(xbp);
   if ((encdata == NULL) || (encdata->size() == 0))
   {
      errprint("encryption of data failed for writing XML descriptor");
      if (encdata) delete encdata;
      return 0;
   }
   apr_status_t status;
   apr_file_t *newFP;
   status = apr_file_open(&newFP, StringUtils::expandMyHomeDirectoryTilde(filename, mp), APR_FOPEN_TRUNCATE|APR_FOPEN_CREATE|APR_FOPEN_WRITE|APR_FOPEN_BINARY|APR_FOPEN_BUFFERED, APR_OS_DEFAULT, mp);
   if (status == 0)
   {
      apr_size_t cesize = encdata->size();
      status = apr_file_write_full(newFP, &(encdata->at(0)), cesize, &cesize);
      CheckAPRError(status);
      status = apr_file_close(newFP);
      CheckAPRError(status);
      int retval = (cesize == encdata->size());
      delete encdata;
      return retval;
   } else
   {
      errprint("problem opening file '%s'", filename);
      delete encdata;
      return 0;
   }
}

int XMLDescriptor::ReadPlain(const char *filename)
{
   return domd->ReadIn(filename);
}

int XMLDescriptor::ReadPlain(const char *buffer, int length)
{
   return domd->ReadIn(buffer, length);
}

int XMLDescriptor::WritePlain(const char *filename)
{
   return domd->WriteOut(filename);
}

char *XMLDescriptor::WritePlain()
{
   xmlBufferPtr xbp = xmlBufferCreate();
   domd->WriteOut(xbp);
   char *newstr = (char *)malloc(sizeof(char)*(xmlBufferLength(xbp)+1));
   memcpy(newstr, (char *)xmlBufferContent(xbp), sizeof(char)*(xmlBufferLength(xbp)));
   newstr[xmlBufferLength(xbp)] = '\0';
   xmlBufferFree(xbp);
   return newstr;
}

char *XMLDescriptor::GetCapabilitiesString()
{
   xmlNodePtr capab = domd->FindNode(domd->GetRootNode(), "capabilities");
   return domd->GetAttribValueString(capab, "capdata");
}

xmlNodePtr XMLDescriptor::GetBlockTransferNode()
{
   xmlNodePtr btrans = domd->FindNode(domd->GetRootNode(), "blocktransfer");
   if (btrans == NULL)
   {
      errprint("could not find 'blocktransfer' node");
      return NULL;
   }
   return btrans;
}

apr_off_t XMLDescriptor::GetNumberOfBlocks()
{
   xmlNodePtr btrans = GetBlockTransferNode();
   if (btrans == NULL) return -1;
   char *blocks = domd->GetAttribValueString(btrans, "blocks");
   if (blocks == NULL)
   {
      errprint("could not find 'blocks' block count node");
      return -1;
   }
   apr_off_t retval = -1;
   sscanf(blocks, "%" APR_OFF_T_FMT, &retval);
   free(blocks);
   return retval;
}

apr_off_t XMLDescriptor::GetUncompressedBlockSize()
{
   xmlNodePtr btrans = GetBlockTransferNode();
   if (btrans == NULL) return -1;
   char *fblocksze = domd->GetAttribValueString(btrans, "fblocksize");
   if (fblocksze == NULL)
   {
      errprint("could not find 'fblocksize' the uncompressed block size node");
      return -1;
   }
   apr_off_t retval = -1;
   sscanf(fblocksze, "%" APR_OFF_T_FMT, &retval);
   free(fblocksze);
   return retval;
}

xmlNodePtr XMLDescriptor::GetBlockNode(int block)
{
   xmlNodePtr btrans = GetBlockTransferNode();
   if (btrans == NULL) return NULL;
   xmlNodePtr blockchild = btrans->xmlChildrenNode;
   while (blockchild != NULL)
   {
      if (xmlStrcmp(blockchild->name, BAD_CAST "block") == 0)
      {
         char *foid = domd->GetAttribValueString(blockchild, "oid");
         if (foid != NULL)
         {
            if (atoi(foid) == block)
            {
               free(foid);
               return blockchild;
            }
            free(foid);
         } else errprint("xml descriptor block has no id number");
      } else errprint("xml descriptor child node is not block");
      blockchild = blockchild->next;
   }
   return NULL;
}

apr_off_t XMLDescriptor::GetCompressedEncryptedSize(int block)
{
   xmlNodePtr blocknode = GetBlockNode(block);
   char *t_cesize = domd->GetAttribValueString(blocknode, "cesize");
   if (t_cesize == NULL)
   {
      errprint("could not find compressed and encrypted size node (cesize)");
      return -1;
   }
   apr_off_t cesize = -1;
   sscanf(t_cesize, "%" APR_OFF_T_FMT, &cesize);
   free(t_cesize);
   return cesize;
}

const char *XMLDescriptor::CalculateXORBlockHash(int numberofblocks)
{
   xmlNodePtr btrans = GetBlockTransferNode();
   if (btrans == NULL) return NULL;
   xmlNodePtr blockchild = btrans->xmlChildrenNode;
   char **blocklist = (char **)malloc(sizeof(char *)*(numberofblocks+1));
   for (int i = 0; i < numberofblocks; i++)
   {
      blocklist[i] = NULL;
      if (xmlStrcmp(blockchild->name, BAD_CAST "block") == 0)
      {
         if (xmlStrcmp(blockchild->xmlChildrenNode->name, BAD_CAST "sha1hash") == 0)
         {
            if (domd->HasAttrib(blockchild->xmlChildrenNode, "val"))
            {
               blocklist[i] = domd->GetAttribValueString(blockchild->xmlChildrenNode, "val");
            } else errprint("expected 'val' attribute");
         } else errprint("expected 'sha1hash' node");
      } else errprint("expected 'block' node");
      blockchild = blockchild->next;
   }
   blocklist[numberofblocks] = NULL;
   char *finalhash = BaseConvert::XORBlockHashes((const char **)blocklist);
   for (int i = 0; i < numberofblocks; i++)
      if (blocklist[i]) free(blocklist[i]);
   free(blocklist);
   return finalhash;
}

int XMLDescriptor::AllocateFileSystemBlocks(AllDBs *dbs, const char *store_uuid, const char *username, int *mustreverse, int *errmsg, apr_pool_t *mp, apr_off_t *sizeused)
{
   apr_off_t extrasize = 0;
   xmlNodePtr btrans = GetBlockTransferNode();
   if (btrans == NULL) return -1;
   xmlNodePtr blockchild = btrans->xmlChildrenNode;
   while (blockchild != NULL)
   {
      if (xmlStrcmp(blockchild->name, BAD_CAST "block") == 0)
         if ((domd->HasAttrib(blockchild, "oid")) && (domd->HasAttrib(blockchild, "cesize")))
         {
            // t_oid and t_cesize cannot be NULL
            char *t_oid = domd->GetAttribValueString(blockchild, "oid");
            char *t_cesize = domd->GetAttribValueString(blockchild, "cesize");
            int oid = atoi(t_oid);
            int cesize = atoi(t_cesize);
            char *tuser = dbs->actorsdb->LookupUltimateUser(username);
            if (cesize > dbs->actorsdb->GetUserQuota(tuser))
            {
               errprint("User '%s' ran out of quota (bytes needed %" APR_OFF_T_FMT " but quota was %" APR_OFF_T_FMT ")", tuser, cesize, dbs->actorsdb->GetUserQuota(tuser));
               if (mustreverse) *mustreverse = 1;
               if (errmsg) *errmsg = PROCESSXMLDESCRIPTOR_OUTOFQUOTA;
            }
            free(t_oid);
            free(t_cesize);
            char *filename = StringUtils::varprintf(ACQUIRE_DYNAMIC_DIR "/" "_%s.c%d", store_uuid, oid);
            Broker4::WriteFakeFile(StringUtils::expandMyHomeDirectoryTilde(filename, mp), cesize, errmsg, mustreverse, mp);
            apr_size_t realsize = Broker4::GetFileSize(filename, mp);
            dbs->actorsdb->ModifyUserQuota(tuser, -((apr_off_t)(realsize)));
            extrasize += ((apr_off_t)(realsize));
            free(tuser);
            free(filename);
         }
      blockchild = blockchild->next;
   }
   if (sizeused) *sizeused = extrasize;
   return 0;
}

int XMLDescriptor::AllocateXMLDescribed(AllDBs *dbs, const char *store_uuid, const char *username, int *errmsg, const char *origination, apr_pool_t *mp, apr_off_t *sizeused)
{
   xmlNodePtr btrans = GetBlockTransferNode();
   if (btrans == NULL) { if (errmsg) *errmsg = PROCESSXMLDESCRIPTOR_BADXML; return -1; }
   int noofblocks = GetNumberOfBlocks();
   if (noofblocks == -1) { if (errmsg) *errmsg = PROCESSXMLDESCRIPTOR_BADXML; return -1; }
   apr_off_t unblocksize = GetUncompressedBlockSize();
   if (unblocksize == -1) { if (errmsg) *errmsg = PROCESSXMLDESCRIPTOR_BADXML; return -1; }
   const char *finalhash = CalculateXORBlockHash(noofblocks);
   char *tuser = dbs->actorsdb->LookupUltimateUser(username);
   if (tuser == NULL)
   {
      errprint("work block does not have an ultimate owner?");
   } else
   {
      /*
      std::vector<char *> *xorbllist = dbs->actorsdb->SQLScanAllTextf("select xorblockhash from work_stores where xorblockhash='%s' and owner='%s';", finalhash, tuser);
      */
      free(tuser);
      /*
      if ((xorbllist) && (xorbllist->size() > 0))
      {
         free((void *)finalhash);
         StringUtils::DestroyStringList(xorbllist);
         if (errmsg) *errmsg = PROCESSXMLDESCRIPTOR_DUPLICATE;
         errprint("work blocked from session creation as duplicate");
         return -1;
      }
      if (xorbllist) delete xorbllist;
      */
   }
   int mustcancel = 0;
   if (errmsg) *errmsg = PROCESSXMLDESCRIPTOR_SUCCESS;
   dbs->actorsdb->AddWorkStore(store_uuid, username, noofblocks, unblocksize, finalhash, origination);
   free((void *)finalhash);
   AllocateFileSystemBlocks(dbs, store_uuid, username, &mustcancel, errmsg, mp, sizeused);
   if (!mustcancel) return 0;
   DBArbitrator::DeleteFileStoreByUUID(dbs, store_uuid, mp);
   if (errmsg) if (*errmsg == PROCESSXMLDESCRIPTOR_SUCCESS) *errmsg = PROCESSXMLDESCRIPTOR_GENERICERROR;
   return -1;
}

int XMLDescriptor::GenerateXMLTreeForBlockTransfer(int *cancel, const char *cname, KeyPair *trialkey, const char *directorypath, apr_size_t blocksize, apr_pool_t *mp, int *allblocks)
{
   apr_status_t status;
   xmlNodePtr tblocktrans = domd->CreateChildNode(domd->GetRootNode(), "blocktransfer");
   char *fblock = StringUtils::varprintf("%" APR_SIZE_T_FMT, blocksize);
   domd->SetProperty(tblocktrans, "fblocksize", fblock);
   free(fblock);

   StreamFromDirectory *sbs = new StreamFromDirectory(directorypath, mp);
   
   apr_size_t totalsize = 0;
   int blockno = 0;
   int asize = blocksize;
   while (asize == blocksize)
   {
      asize = blocksize;
      int cesize = 0;
      char *hash = NULL;
      char *cemem_block = sbs->GetNextBlockEncrypted(cname, trialkey, &asize, &cesize, &hash);
      xmlNodePtr tblock = domd->CreateChildNode(tblocktrans, "block");
      // This scheme will flop if the workpacket >2Pb! 1Mb * 2 billion, have to resize blocks
      char *buf = NULL;
      buf = StringUtils::varprintf("%d", blockno);
      domd->SetProperty(tblock, "oid", buf); free(buf);
      buf = StringUtils::varprintf("%d", asize);
      domd->SetProperty(tblock, "tsize", buf); free(buf);
      buf = StringUtils::varprintf("%d", cesize);
      domd->SetProperty(tblock, "cesize", buf); free(buf);
      xmlNodePtr xhash = domd->CreateChildNode(tblock, "sha1hash");
      domd->SetProperty(xhash, "val", hash);
      printf("BLOCK %d - size: %d hash: %s compressed: %d ratio: %2.2f\n", blockno, asize, hash, cesize, (double)asize / (double)cesize);
      free(hash);
      free(cemem_block);
      blockno++;
      totalsize += asize;
      if (allblocks) *allblocks = blockno;
      if (cancel)
         if (*cancel)
            break;
   }
   char *bbuf = NULL;
   bbuf = StringUtils::varprintf("%d", blockno);
   domd->SetProperty(tblocktrans, "blocks", bbuf); free(bbuf);
   bbuf = StringUtils::varprintf("%" APR_SIZE_T_FMT, totalsize);
   domd->SetProperty(tblocktrans, "fsize", bbuf); free(bbuf);
   delete sbs;
   return 0;
}

int XMLDescriptor::UnlinkXMLTreeForBlockTransfer()
{
   xmlNodePtr btrans = GetBlockTransferNode();
   if (btrans == NULL) return -1;
   xmlUnlinkNode(btrans); xmlFreeNode(btrans);
   return 0;
}

void XMLDescriptor::GenerateInitialDescriptor(const char *workname, const char *examplechain, const char *caps, int secondsmax)
{
   domd->GenerateRootNode("workpacket");
   xmlNodePtr troot = domd->GetRootNode();
   if ((workname == NULL) || (strlen(workname) == 0))
   {
      errprint("workname not given, defaulting to 'untitled work'");
      domd->SetProperty(troot, "localname", "untitled work");
   } else
   {
      domd->SetProperty(troot, "localname", workname);
   }
   char timebuffer[64+9];
   if (secondsmax < 300)
   {
      errprint("seconds given is less than five minutes, rounding up to five minutes");
      sprintf(timebuffer, "00:05:00");
   } else
   {
      sprintf(timebuffer, "%02d:%02d:%02d", ((secondsmax / 60) / 24), (secondsmax / 60) - (((secondsmax / 60) / 24) * 24), secondsmax % 60);
   }
   domd->SetProperty(troot, "maxtime", timebuffer);
   xmlNodePtr tcapsnode = domd->CreateChildNode(troot, "capabilities");
   if ((examplechain == NULL) || (strlen(examplechain) == 0))
   {
      errprint("examplechain empty, defaulting to 'unknown'");
      domd->SetProperty(tcapsnode, "examplechain", "unknown");
   } else
   {
      domd->SetProperty(tcapsnode, "examplechain", examplechain);
   }
   if ((caps == NULL) || (strlen(caps) == 0))
   {
      errprint("capabilities have not been specified, defaulting to most basic 'single' required");
      domd->SetProperty(tcapsnode, "capdata", "single");
   } else
   {
      domd->SetProperty(tcapsnode, "capdata", caps);
   }
}
